﻿using System.Diagnostics.Contracts;
using System.IO;
using System.Reactive.Linq;

namespace System.Net
{
#if !SILVERLIGHT
	/// <summary>
	/// Provides <see langword="static"/> extension methods that return observable sequences for various asynchronous methods defined 
	/// by the <see cref="WebRequest"/>, <see cref="FtpWebRequest"/> and <see cref="HttpWebRequest"/> classes.
	/// </summary>
	public static class WebRequestExtensions
#else
	/// <summary>
	/// Provides <see langword="static"/> extension methods that return observable sequences for various asynchronous methods defined 
	/// by the <see cref="WebRequest"/> and <see cref="HttpWebRequest"/> classes.
	/// </summary>
	public static class WebRequestExtensions
#endif
	{
		/// <summary>
		/// Creates a <see cref="Stream"/> for writing data to the Internet resource.
		/// </summary>
		/// <param name="request">The <see cref="WebRequest"/> that provides the writable stream.</param>
		/// <returns>A singleton observable containing the writable <see cref="Stream"/>.</returns>
		public static IObservable<Stream> GetRequestStreamObservable(this WebRequest request)
		{
			Contract.Requires(request != null);
			Contract.Ensures(Contract.Result<IObservable<Stream>>() != null);

			var invoke = Observable.FromAsyncPattern<Stream>(request.BeginGetRequestStream, request.EndGetRequestStream);

			var observable = invoke();

			Contract.Assume(observable != null);

			return observable;
		}

#if !SILVERLIGHT
		/// <summary>
		/// Creates a <see cref="Stream"/> for writing data to the Internet resource.
		/// </summary>
		/// <param name="request">The <see cref="HttpWebRequest"/> that provides the writable stream.</param>
		/// <returns>A singleton observable containing a tuple with the writable <see cref="Stream"/> as the first item
		/// and the <see cref="TransportContext"/> as the second item.</returns>
		public static IObservable<Tuple<Stream, TransportContext>> GetRequestStreamObservable(this HttpWebRequest request)
		{
			Contract.Requires(request != null);
			Contract.Ensures(Contract.Result<IObservable<Tuple<Stream, TransportContext>>>() != null);

			TransportContext context = null;

			var invoke = Observable.FromAsyncPattern<Stream>(
				request.BeginGetRequestStream,
				asyncResult =>
				{
					return request.EndGetRequestStream(asyncResult, out context);
				});

			var observable = invoke();

			Contract.Assume(observable != null);

			return observable.Select(stream => Tuple.Create(stream, context));
		} 
#endif

		/// <summary>
		/// Returns a response to an Internet request.
		/// </summary>
		/// <param name="request">The <see cref="WebRequest"/> that creates the response.</param>
		/// <returns>A singleton observable containing the response.</returns>
		public static IObservable<WebResponse> GetResponseObservable(this WebRequest request)
		{
			Contract.Requires(request != null);
			Contract.Ensures(Contract.Result<IObservable<WebResponse>>() != null);

			var invoke = Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse);

			var observable = invoke();

			Contract.Assume(observable != null);

			return observable;
		}

		/// <summary>
		/// Returns a response to an Internet request.
		/// </summary>
		/// <param name="request">The <see cref="HttpWebRequest"/> that creates the response.</param>
		/// <returns>A singleton observable containing the response.</returns>
		public static IObservable<HttpWebResponse> GetResponseObservable(this HttpWebRequest request)
		{
			Contract.Requires(request != null);
			Contract.Ensures(Contract.Result<IObservable<HttpWebResponse>>() != null);

			var invoke = Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse);

#if !WINDOWS_PHONE
			var observable = invoke();

			Contract.Assume(observable != null);

			return observable.Cast<HttpWebResponse>();
#else
			var observable = invoke();

			Contract.Assume(observable != null);

			return observable.Cast<WebResponse, HttpWebResponse>();
#endif
		}

#if !SILVERLIGHT
		/// <summary>
		/// Returns the FTP server response.
		/// </summary>
		/// <param name="request">The <see cref="FtpWebRequest"/> that creates the response.</param>
		/// <returns>A singleton observable containing the response.</returns>
		public static IObservable<FtpWebResponse> GetResponseObservable(this FtpWebRequest request)
		{
			Contract.Requires(request != null);
			Contract.Ensures(Contract.Result<IObservable<FtpWebResponse>>() != null);

			var invoke = Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse);

			var observable = invoke();

			Contract.Assume(observable != null);

			return observable.Cast<FtpWebResponse>();
		}
#endif
	}
}